/*
 * This file contains classes and functions that represent the 'model' 
 * of this application.
 */

// Generator used for model element identification
var defaultUidGenerator = new UIDGenerator(100000);

// ======================== MODEL ELEMENT ========================
// 
// Base class for all model element classes
function ModelElement(name) {
    this.uid = defaultUidGenerator.generateUID();
    this.name = name;
    this.stereotypes = new Array();
    this.eventDispatcher = new EventDispatcher();
    this.model = null;
    // ---
}
// Notify listeners that one of this object's property has been changed
ModelElement.prototype.notifyPropertyChanged = function(propName) {
    var ev = new Event("propertyChanged", this);
    ev.property = propName;
    this.eventDispatcher.dispatchEvent(ev);
};
// Notify listeners that this object has been destructed
ModelElement.prototype.notifyDestroyed = function() {
    var ev = new Event("destroyed", this);
    this.eventDispatcher.dispatchEvent(ev);
};
// Object 'destructor' 
ModelElement.prototype.destroy = function() {

    for (var i = 0; i < this.stereotypes.length; i++) {
        this.stereotypes[i].destroy();
    }

    if (this.model !== null) {
        this.model.removeElement(this);
    }

    this.stereotypes = null;

    this.notifyDestroyed();
};
// Event callback
ModelElement.prototype.handleEvent = function(event) {

};

ModelElement.prototype.getStereotypeById = function(id) {
    for (var i = 0; i < this.stereotypes.length; i++) {
        // Note: this equality check is intentionally not checking type 
        // as it would not accept string-int equality.
        if (this.stereotypes[i].uid == id) {
            return this.stereotypes[i];
        }
    }
    return null;
};
ModelElement.prototype.setName = function(name) {
    this.name = name;
    this.notifyPropertyChanged("name");
};
ModelElement.prototype.setUid = function(uid) {
    this.uid = uid;
    this.notifyPropertyChanged("uid");
};
ModelElement.prototype.addStereotype = function(stereotype) {
    stereotype.attachedTo = this;
    this.stereotypes.push(stereotype);
    stereotype.eventDispatcher.registerListener(this);
    this.notifyPropertyChanged("stereotypes");
};
ModelElement.prototype.removeStereotype = function(stereotype) {
    this.stereotypes.remove(stereotype);
    stereotype.eventDispatcher.removeListener(this);
    this.notifyPropertyChanged("stereotypes");
};
ModelElement.prototype.toString = function() {
    return "[ModelElement uid=" + this.uid + ", name=" + this.name + "]";
};

// ======================== STEREOTYPE ========================
// 
// Represents a stereotype object assigned to a model element
// Note: there is one instance of this type of object for every 
//       unique stereotype assigned to elements
Stereotype.prototype = new ModelElement;
function Stereotype(name) {
    this.baseClass = ModelElement;
    this.baseClass(name);
    // ---
    this.attachedTo = null;
}
Stereotype.prototype.destroy = function() {
    this.attachedTo = null;
    this.constructor.prototype.destroy.call(this);
};
Stereotype.prototype.handleEvent = function(event) {
    // nop
    this.constructor.prototype.handleEvent.call(this, event);
};
Stereotype.prototype.toString = function() {
    return "[Stereotype uid=" + this.uid + ", name=" + this.name + "]";
};

// ======================== CLASSIFIER ========================
// 
// Base class for objects that represent some sort of type on the 
// diagram: primitives, classes, interfaces.
Classifier.prototype = new ModelElement;
function Classifier(name) {
    this.baseClass = ModelElement;
    this.baseClass(name);
    // ---
}
//Classifier.prototype.destroy = function() {
//    // just call parent
//    this.constructor.prototype.destroy.call(this);
//};
//Classifier.prototype.handleEvent = function(event) {
//    // nop
//    this.constructor.prototype.handleEvent.call(this,event);
//};
Classifier.prototype.toString = function() {
    return "[Classifier uid=" + this.uid + ", name=" + this.name + "]";
};

// ======================== CLASS ========================
// 
// Represents a class entity.
Class.prototype = new Classifier;
function Class(name, isInterface) {
    this.baseClass = Classifier;
    this.baseClass(name);
    // ---
    this.visibility = "+";
    this.isAbstract = false;
    this.isInterface = (isInterface !== undefined) ? isInterface : false;
    this.attributes = new Array();
    this.operations = new Array();
    this.relationships = new Array();
}
Class.prototype.destroy = function() {

    this.attributes.concurrentCallAllAndClear(function(e) {
        e.destroy();
    });
    this.operations.concurrentCallAllAndClear(function(e) {
        e.destroy();
    });
    this.relationships.concurrentCallAllAndClear(function(e) {
        e.destroy();
    });

    this.attributes = null;
    this.operations = null;
    this.relationships = null;

    this.constructor.prototype.destroy.call(this);
};
Class.prototype.handleEvent = function(event) {
    // nop
    if (event.type === "propertyChanged" || event.type === "embeddedPropertyChanged") {
        var e = new Event("embeddedPropertyChanged", this);
        this.eventDispatcher.dispatchEvent(e);
    }
};
Class.prototype.getAttributeById = function(id) {
    for (var i = 0; i < this.attributes.length; i++) {
        // Note: this equality check is intentionally not checking type 
        // as it would not accept string-int equality.
        if (this.attributes[i].uid == id) {
            return this.attributes[i];
        }
    }
    return null;
};
Class.prototype.getOperationById = function(id) {
    for (var i = 0; i < this.operations.length; i++) {
        // Note: this equality check is intentionally not checking type 
        // as it would not accept string-int equality.
        if (this.operations[i].uid == id) {
            return this.operations[i];
        }
    }
    return null;
};
Class.prototype.setVisibility = function(vis) {
    this.visibility = vis;
    this.notifyPropertyChanged("visibility");
};
Class.prototype.setIsAbstract = function(isabstract) {
    this.isAbstract = isabstract;
    this.notifyPropertyChanged("isAbstract");
};
Class.prototype.addOperation = function(op) {
    this.operations.push(op);
    op.eventDispatcher.registerListener(this);
    this.notifyPropertyChanged("operations");
};
Class.prototype.removeOperation = function(op) {
    this.operations.remove(op);
    op.eventDispatcher.removeListener(this);
    this.notifyPropertyChanged("operations");
};
Class.prototype.addAttribute = function(attr) {
    this.attributes.push(attr);
    attr.eventDispatcher.registerListener(this);
    this.notifyPropertyChanged("attributes");
};
Class.prototype.removeAttribute = function(attr) {
    this.attributes.remove(attr);
    attr.eventDispatcher.removeListener(this);
    this.notifyPropertyChanged("attributes");
};
Class.prototype.addRelationship = function(rel) {
    this.relationships.push(rel);
};
Class.prototype.removeRelationship = function(rel) {
    this.relationships.remove(rel);
};
Class.prototype.toString = function() {
    return "[Class uid=" + this.uid + ", name=" + this.name + ", isInterface=" + this.isInterface + ", isAbstract=" + this.isAbstract + "]";
};

// ======================== OPERATION ========================
// 
// Represents an operation (method in some languages) entity.
Operation.prototype = new ModelElement;
function Operation(name) {
    this.baseClass = ModelElement;
    this.baseClass(name);
    // ---
    this.visibility = "+";
    this.isAbstract = false;
    this.isClassifierScope = false;
    this.returnType = PrimitiveType.void;
    this.parameters = new Array();
}
Operation.prototype.destroy = function() {

    if (this.returnType !== null)
        this.returnType.eventDispatcher.removeListener(this);
    this.parameters.concurrentCallAllAndClear(function(e) {
        e.destroy();
    });
    this.parameters = null;
    this.constructor.prototype.destroy.call(this);
};
Operation.prototype.handleEvent = function(event) {
    // if returnType is destroyed -> set it to null
    if (event.type === "destroyed") {
        if (event.source === this.returnType) {
            this.setReturnType(null);
        }
    } else if (event.type === "propertyChanged") {
//        this.notifyPropertyChanged("parameters");
        var e = new Event("embeddedPropertyChanged", this);
        this.eventDispatcher.dispatchEvent(e);
    } else if (event.type === "embeddedPropertyChanged" &&event.source instanceof Parameter) {
//        this.notifyPropertyChanged("parameters");
        var e = new Event("embeddedPropertyChanged", this);
        this.eventDispatcher.dispatchEvent(e);
    }
};
Operation.prototype.getParameterById = function(id) {
    for (var i = 0; i < this.parameters.length; i++) {
        // Note: this equality check is intentionally not checking type 
        // as it would not accept string-int equality.
        if (this.parameters[i].uid == id) {
            return this.parameters[i];
        }
    }
    return null;
};
Operation.prototype.setVisibility = function(vis) {
    this.visibility = vis;
    this.notifyPropertyChanged("visibility");
};
Operation.prototype.setIsAbstract = function(isabstract) {
    this.isAbstract = isabstract;
    this.notifyPropertyChanged("isAbstract");
};
Operation.prototype.setIsClassifierScope = function(isclassifier) {
    this.isClassifierScope = isclassifier;
    this.notifyPropertyChanged("isClassifierScope");
};
Operation.prototype.setReturnType = function(rettype) {
    if (this.returnType !== null)
        this.returnType.eventDispatcher.removeListener(this);
    this.returnType = rettype;
    if (this.returnType !== null)
        this.returnType.eventDispatcher.registerListener(this);
    this.notifyPropertyChanged("returnType");
};
Operation.prototype.addParameter = function(par) {
    this.parameters.push(par);
    par.eventDispatcher.registerListener(this);
    this.notifyPropertyChanged("parameters");
};
Operation.prototype.removeParameter = function(par) {
    this.parameters.remove(par);
    par.eventDispatcher.removeListener(this);
    this.notifyPropertyChanged("parameters");
};
Operation.prototype.toString = function() {
    return "[Operation uid=" + this.uid + ", name=" + this.name + ", parameters=" + this.parameters + "]";
};


// ======================== ATTRIBUTE ========================
// 
// Represents an attribute (field, member in some languages) entity.
Attribute.prototype = new ModelElement;
function Attribute(name) {
    this.baseClass = ModelElement;
    this.baseClass(name);
    // ---
    this.visibility = "-";
    this.isClassifierScope = false;
    this.defaultValue = null;
    this.type = PrimitiveType.integer;
    this.multiplicity = null;
}

Attribute.prototype.destroy = function() {
    // just call parent
    if (this.type !== null)
        this.type.eventDispatcher.removeListener(this);
    this.constructor.prototype.destroy.call(this);
};
Attribute.prototype.handleEvent = function(event) {
    // if type is destroyed -> set it to null

    if (event.type === "destroyed") {
        if (event.source === this.type) {
            this.setType(null);
        }
    } else if (event.type === "propertyChanged") {
        if(event.source === this.type) {
            var e = new Event("embeddedPropertyChanged", this);
            this.eventDispatcher.dispatchEvent(e);
        } else if(event.source instanceof Stereotype) {
            var e = new Event("embeddedPropertyChanged", this);
            this.eventDispatcher.dispatchEvent(e);
        }
    }
};

Attribute.prototype.setVisibility = function(vis) {
    this.visibility = vis;
    this.notifyPropertyChanged("visibility");
};
Attribute.prototype.setIsClassifierScope = function(isclassifier) {
    this.isClassifierScope = isclassifier;
    this.notifyPropertyChanged("isClassifierScope");
};
Attribute.prototype.setDefaultValue = function(val) {
    this.defaultValue = val;
    this.notifyPropertyChanged("defaultValue");
};
Attribute.prototype.setType = function(type) {
    if (this.type !== null)
        this.type.eventDispatcher.removeListener(this);
    this.type = type;
    if (this.type !== null)
        this.type.eventDispatcher.registerListener(this);
    this.notifyPropertyChanged("type");
};
Attribute.prototype.setMultiplicity = function(mul) {
    this.multiplicity = mul;
    this.notifyPropertyChanged("multiplicity");
};

Attribute.prototype.toString = function() {
    return "[Attribute uid=" + this.uid + ", name=" + this.name + "]";
};


// ======================== PARAMETER ========================
// 
// Represents a parameter of an operation.
Parameter.prototype = new ModelElement;
function Parameter(name) {
    this.baseClass = ModelElement;
    this.baseClass(name);
    // ---
    this.defaultValue = null;
    this.type = PrimitiveType.integer;
}
Parameter.prototype.destroy = function() {
    // just call parent
    if (this.type !== null)
        this.type.eventDispatcher.removeListener(this);
    this.constructor.prototype.destroy.call(this);
};
Parameter.prototype.handleEvent = function(event) {
    // if type is destroyed -> set it to null
    if (event.type === "destroyed") {
        if (event.source === this.type) {
            this.setType(null);
        }
    } else if (event.type === "propertyChanged") {
        if(event.source === this.type) {
            var e = new Event("embeddedPropertyChanged", this);
            this.eventDispatcher.dispatchEvent(e);
        } else if(event.source instanceof Stereotype) {
            var e = new Event("embeddedPropertyChanged", this);
            this.eventDispatcher.dispatchEvent(e);
        }
    }
};
Parameter.prototype.setDefaultValue = function(val) {
    this.defaultValue = val;
    this.notifyPropertyChanged("defaultValue");
};
Parameter.prototype.setType = function(type) {
    if (this.type !== null)
        this.type.eventDispatcher.removeListener(this);
    this.type = type;
    if (this.type !== null)
        this.type.eventDispatcher.registerListener(this);
    this.notifyPropertyChanged("type");
};

Parameter.prototype.toString = function() {
    return "[Parameter uid=" + this.uid + ", name=" + this.name + "]";
};

// ======================== RELATIONSHIP ========================
// 
// Represents a relationship between classes/interfaces
Relationship.prototype = new ModelElement;
function Relationship(name) {
    this.baseClass = ModelElement;
    this.baseClass(name);
    // ---
    this.sourceEnd = null;
    this.targetEnd = null;
}

Relationship.prototype.destroy = function() {
    if (this.sourceEnd !== null) {
        this.sourceEnd.eventDispatcher.removeListener(this);
        this.sourceEnd.removeRelationship(this);
    }
    if (this.targetEnd !== null) {
        this.targetEnd.eventDispatcher.removeListener(this);
        this.targetEnd.removeRelationship(this);
    }
    this.sourceEnd = null;
    this.targetEnd = null;
    this.constructor.prototype.destroy.call(this);
};
Relationship.prototype.handleEvent = function(event) {
    // nop
     if (event.type === "propertyChanged") {
        var e = new Event("embeddedPropertyChanged", this);
        this.eventDispatcher.dispatchEvent(e);
    }
};

Relationship.prototype.setSourceEnd = function(end) {
    this.sourceEnd = end;
    this.sourceEnd.addRelationship(this);
    this.sourceEnd.eventDispatcher.registerListener(this);
    this.notifyPropertyChanged("sourceEnd");
};
Relationship.prototype.setTargetEnd = function(end) {
    this.targetEnd = end;
    this.targetEnd.addRelationship(this);
    this.targetEnd.eventDispatcher.registerListener(this);
    this.notifyPropertyChanged("targetEnd");
};

Relationship.prototype.toString = function() {
    return "[Relationship uid=" + this.uid + ", name=" + this.name + ", sourceEnd=" + this.sourceEnd + ", targetEnd=" + this.targetEnd + "]";
};

// ======================== INHERITANCE ========================
// 
// Represents an inheritance (generalization between classes or interfaces, 
// or realization between a class and an interface.
Inheritance.prototype = new Relationship;
function Inheritance(name, isGen, isReal) {
    this.baseClass = Relationship;
    this.baseClass(name);
    // ---
    this.isGeneralization = isGen;
    this.isRealization = isReal;
}

//Inheritance.prototype.destroy = function() {
//    // just call parent
//    this.constructor.prototype.destroy.call(this);
//};
//Inheritance.prototype.handleEvent = function(event) {
//    // nop
//};

Inheritance.prototype.toString = function() {
    return "[Inheritance uid=" + this.uid + ", name=" + this.name + ", isGeneralization=" + this.isGeneralization + ", isRealization=" + this.isRealization + ", sourceEnd=" + this.sourceEnd + ", targetEnd=" + this.targetEnd + "]";
};


// ======================== ASSOCIATION ========================
// 
// Represents an association between classes.

// Helper class: represents an end of the association
function AssociationEnd(parent, end) {
    this.parent = parent;
    this.end = end;
    this.name = null;
    this.visibility = "-";
    this.multiplicity = null;
    this.isNavigable = true;
}
AssociationEnd.prototype.setName = function(name) {
    this.name = name;
    this.parent.notifyPropertyChanged(this.end + "EndInfo.name");
};
AssociationEnd.prototype.setVisibility = function(visibility) {
    this.visibility = visibility;
    this.parent.notifyPropertyChanged(this.end + "EndInfo.visibility");
};
AssociationEnd.prototype.setMultiplicity = function(multiplicity) {
    this.multiplicity = multiplicity;
    this.parent.notifyPropertyChanged(this.end + "EndInfo.multiplicity");
};
AssociationEnd.prototype.setIsNavigable = function(isNavigable) {
    this.isNavigable = isNavigable;
    this.parent.notifyPropertyChanged(this.end + "EndInfo.isNavigable");
};

Association.prototype = new Relationship;
function Association(name, isAssoc, isAggr, isComp) {
    this.baseClass = Relationship;
    this.baseClass(name);
    // ---
    this.isAssociation = isAssoc;
    this.isAggregation = isAggr;
    this.isComposition = isComp;
    this.sourceEndInfo = new AssociationEnd(this, "source");
    this.targetEndInfo = new AssociationEnd(this, "target");
}
//Association.prototype.destroy = function() {
//    // just call parent
//    this.constructor.prototype.destroy.call(this);
//};
//Association.prototype.handleEvent = function(event) {
//    // nop
//};

Association.prototype.setIsAssociation = function(isAssoc) {
    this.isAssociation = isAssoc;
    this.notifyPropertyChanged("isAssociation");
};
Association.prototype.setIsAggregation = function(isAggr) {
    this.isAggregation = isAggr;
    this.notifyPropertyChanged("isAggregation");
};
Association.prototype.setIsComposition = function(isComp) {
    this.isComposition = isComp;
    this.notifyPropertyChanged("isComposition");
};

Association.prototype.toString = function() {
    return "[Association uid=" + this.uid + ", name=" + this.name + ", isAssociation=" + this.isAssociation + ", isAggregation=" + this.isAggregation + ", isComposition=" + this.isComposition + ", sourceEnd=" + this.sourceEnd + ", targetEnd=" + this.targetEnd + "]";
};

// ======================== DEPENDENCY ========================
// 
// Represents an dependency between classes.
Dependency.prototype = new Relationship;
function Dependency(name) {
    this.baseClass = Relationship;
    this.baseClass(name);
    // ---
}
//Dependency.prototype.destroy = function() {
//    // just call parent
//    this.constructor.prototype.destroy.call(this);
//};
//Dependency.prototype.handleEvent = function(event) {
//    // nop
//};
Dependency.prototype.toString = function() {
    return "[Dependency uid=" + this.uid + ", name=" + this.name + ", sourceEnd=" + this.sourceEnd + ", targetEnd=" + this.targetEnd + "]";
};

// ======================== PRIMITIVE TYPE ========================
// 
// Represents a class entity.
PrimitiveType.prototype = new Classifier;
function PrimitiveType(name) {
    this.baseClass = Classifier;
    this.baseClass(name);
    // ---
}
//PrimitiveType.prototype.destroy = function() {
//    // just call parent
//    this.constructor.prototype.destroy.call(this);
//};
//PrimitiveType.prototype.handleEvent = function(event) {
//    // nop
//};
PrimitiveType.prototype.toString = function() {
    return "[PrimitiveType uid=" + this.uid + ", name=" + this.name + "]";
};

// ======================== MODEL ========================
// 
// Represents a class entity.
function Model() {
    // elements stored in a 'kind-of' map<String,ModelElement>
    this.modelElementsMap = new Object();
    this.modelElements = new Array();
    this.eventDispatcher = new EventDispatcher();
    // ---
}
// Notify listeners that one of this object's property has been changed
Model.prototype.notifyModelChanged = function(modification,elementInvolved) {
    var ev = new Event("modelChanged", this);
    ev.modification = modification;
    ev.elementInvolved = elementInvolved;
    this.eventDispatcher.dispatchEvent(ev);
};

Model.prototype.getElementById = function(id) {
    if (id !== undefined && id !== null) {
        return this.modelElementsMap[id];
    }
};
Model.prototype.getElementByIdFromAll = function(id) {
    var element;
    for (var i = 0; this.modelElements[i]; i++)
    {
        // alert("element");
        element = this.modelElements[i];
        if (element.uid === id) {
            // alert("hit");
            return element;
        }

        // Some elements doesn't have attributes
        try {
            var attribute;
            for (var j = 0; element.attributes[j]; j++)
            {
                // alert("attribute");
                attribute = element.attributes[j];
                if (attribute.uid === id) {
                    // alert("hit");
                    return attribute;
                }
            }
        } catch (e) {
        }

        // Some elements doesn't have operations
        try {
            var operation;
            for (var j = 0; element.operations[j]; j++)
            {
                // alert("operation");
                operation = element.operations[j];
                if (operation.uid === id) {
                    // alert("hit");
                    return operation;
                }

                var parameter;
                for (var k = 0; operation.parameters[k]; k++)
                {
                    // alert("parameter");
                    parameter = operation.parameters[k];
                    if (parameter.uid === id) {
                        // alert("hit");
                        return parameter;
                    }
                }
            }
        } catch (e) {
        }
    }
    
    return null;
};

Model.prototype.addElement = function(element) {
    if (element !== undefined && element !== null) {
        element.model = this;
        this.modelElementsMap[element.uid] = element;
        this.modelElements.push(element);
        this.notifyModelChanged("add",element);
    }
};
Model.prototype.removeElement = function(element) {
    if (element !== undefined && element !== null) {
        if (this.modelElementsMap[element.uid]) {
            this.modelElements.remove(element);
            this.notifyModelChanged("remove",element);
        }
        this.modelElementsMap[element.uid] = null;
    }
};

Model.prototype.removeAndDestroyElement = function(element) {
    if (element !== undefined && element !== null) {
        if (this.modelElementsMap[element.uid] !== undefined && this.modelElementsMap[element.uid] !== null) {
            var e = this.modelElementsMap[element.uid];
            this.modelElementsMap[element.uid] = null;
            this.modelElements.remove(element);
            this.notifyModelChanged("remove",element);
            e.destroy();
        }
    }
};

Model.prototype.toString = function() {
    var s = "";
    s += "[Model elements=";
    for (id in this.modelElementsMap) {
        var obj = this.modelElementsMap[id];
        s += "<br/>&nbsp;&nbsp;&nbsp;'" + id + "' ---> " + obj;
    }
    s += "<br/>]";
    return s;
};

// ======================== PRIMITIVE TYPES ========================

// Create built-in primitive types

PrimitiveType.boolean = new PrimitiveType("boolean");
PrimitiveType.integer = new PrimitiveType("integer");
PrimitiveType.real = new PrimitiveType("real");
PrimitiveType.string = new PrimitiveType("string");
PrimitiveType.void = new PrimitiveType("void");

PrimitiveType.boolean.uid = 1;
PrimitiveType.integer.uid = 2;
PrimitiveType.real.uid = 3;
PrimitiveType.string.uid = 4;
PrimitiveType.void.uid = 5;

// Set generator back to its initial state
defaultUidGenerator.setNextValue(100000);


// ======================== XML WRITING FUNCTIONS ========================
// 
//

ModelElement.prototype.toXml = function(xmlWriter) {
//    throw "ModelElement.toXmlString unimplemented"
};

Stereotype.prototype.toXml = function(xmlWriter) {
    xmlWriter.beginNode("stereotype");
    xmlWriter.addAttribute("id", this.uid);
    if (this.attachedTo !== null)
        xmlWriter.addAttribute("attachRef", resolveTypeRef(this.attachedTo.uid));
    xmlWriter.addAttribute("name", this.name);
    xmlWriter.endNode();

};

Class.prototype.toXml = function(xmlWriter) {
    if (this.isInterface)
        xmlWriter.beginNode("interface");
    else
        xmlWriter.beginNode("class");

    xmlWriter.addAttribute("id", this.uid);
    xmlWriter.addAttribute("name", this.name);
    xmlWriter.addAttribute("visibility", convertVisibilityModelToXml(this.visibility));
    if (!this.isInterface)
        xmlWriter.addAttribute("isAbstract", this.isAbstract);

    xmlWriter.beginNodeChildren();
    for (var i = 0; i < this.attributes.length; i++) {
        this.attributes[i].toXml(xmlWriter);
    }
    for (var i = 0; i < this.operations.length; i++) {
        this.operations[i].toXml(xmlWriter);
    }
    xmlWriter.endNodeChildren();

    xmlWriter.endNode();
};

Attribute.prototype.toXml = function(xmlWriter) {
    xmlWriter.beginNode("attribute");

    xmlWriter.addAttribute("id", this.uid);
    xmlWriter.addAttribute("name", this.name);
    xmlWriter.addAttribute("visibility", convertVisibilityModelToXml(this.visibility));
    xmlWriter.addAttribute("isClassifier", this.isClassifierScope ? "true" : "false");
    if (this.type !== null)
        xmlWriter.addAttribute("typeRef", resolveTypeRef(this.type.uid));
    if (this.defaultValue !== null)
        xmlWriter.addAttribute("defaultValue", this.defaultValue);
    if (this.multiplicity !== null)
        xmlWriter.addAttribute("multiplicity", this.multiplicity);

    xmlWriter.endNode();
};

Operation.prototype.toXml = function(xmlWriter) {
    xmlWriter.beginNode("operation");

    xmlWriter.addAttribute("id", this.uid);
    xmlWriter.addAttribute("name", this.name);
    xmlWriter.addAttribute("visibility", convertVisibilityModelToXml(this.visibility));
    xmlWriter.addAttribute("isClassifier", this.isClassifierScope ? "true" : "false");
    xmlWriter.addAttribute("isAbstract", this.isAbstract);

    if (this.returnType !== null)
        xmlWriter.addAttribute("returnTypeRef", resolveTypeRef(this.returnType.uid));

    xmlWriter.beginNodeChildren();
    for (var i = 0; i < this.parameters.length; i++) {
        this.parameters[i].toXml(xmlWriter);
    }
    xmlWriter.endNodeChildren();

    xmlWriter.endNode();
};

Parameter.prototype.toXml = function(xmlWriter) {
    xmlWriter.beginNode("parameter");

    xmlWriter.addAttribute("id", this.uid);
    xmlWriter.addAttribute("name", this.name);
    if (this.type !== null)
        xmlWriter.addAttribute("typeRef", resolveTypeRef(this.type.uid));
    if (this.defaultValue !== null)
        xmlWriter.addAttribute("defaultValue", this.defaultValue);

    xmlWriter.endNode();
};

Inheritance.prototype.toXml = function(xmlWriter) {
    if (this.isGeneralization) {
        xmlWriter.beginNode("inheritance");
    } else if (this.isRealization) {
        xmlWriter.beginNode("inheritance");
    } else {
        // error
        return "";
    }

    xmlWriter.addAttribute("id", this.uid);
    xmlWriter.addAttribute("name", this.name);
    if (this.sourceEnd !== null)
        xmlWriter.addAttribute("sourceRef", resolveTypeRef(this.sourceEnd.uid));
    if (this.targetEnd !== null)
        xmlWriter.addAttribute("targetRef", resolveTypeRef(this.targetEnd.uid));

    xmlWriter.addAttribute("isGeneralization", this.isGeneralization);
    xmlWriter.addAttribute("isRealization", this.isRealization);

    xmlWriter.endNode();
};

Association.prototype.toXml = function(xmlWriter) {
    if (this.isAssociation) {
        xmlWriter.beginNode("association");
    } else if (this.isAggregation) {
        xmlWriter.beginNode("association");
    } else if (this.isComposition) {
        xmlWriter.beginNode("association");
    } else {
        // error
        return "";
    }

    xmlWriter.addAttribute("id", this.uid);
    xmlWriter.addAttribute("name", this.name);

    xmlWriter.beginNodeChildren();

    xmlWriter.beginNode("association-end");
    xmlWriter.addAttribute("end", "source");
    if (this.sourceEnd !== null)
        xmlWriter.addAttribute("typeRef", resolveTypeRef(this.sourceEnd.uid));
    if (this.sourceEndInfo.name !== null)
        xmlWriter.addAttribute("name", this.sourceEndInfo.name);
    xmlWriter.addAttribute("visibility", convertVisibilityModelToXml(this.sourceEndInfo.visibility));
    if (this.sourceEndInfo.multiplicity !== null)
        xmlWriter.addAttribute("multiplicity", this.sourceEndInfo.multiplicity);
    xmlWriter.addAttribute("isNavigable", this.sourceEndInfo.isNavigable);
    xmlWriter.endNode();

    xmlWriter.beginNode("association-end");
    xmlWriter.addAttribute("end", "target");
    if (this.targetEnd !== null)
        xmlWriter.addAttribute("typeRef", resolveTypeRef(this.targetEnd.uid));
    if (this.targetEndInfo.name !== null)
        xmlWriter.addAttribute("name", this.targetEndInfo.name);
    xmlWriter.addAttribute("visibility", convertVisibilityModelToXml(this.targetEndInfo.visibility));
    if (this.targetEndInfo.multiplicity !== null)
        xmlWriter.addAttribute("multiplicity", this.targetEndInfo.multiplicity);
    xmlWriter.addAttribute("isNavigable", this.targetEndInfo.isNavigable);
    xmlWriter.endNode();

    xmlWriter.endNodeChildren();

    xmlWriter.addAttribute("isAssociation", this.isAssociation);
    xmlWriter.addAttribute("isAggregation", this.isAggregation);
    xmlWriter.addAttribute("isComposition", this.isComposition);

    xmlWriter.endNode();
};

Dependency.prototype.toXml = function(xmlWriter) {
    xmlWriter.beginNode("dependency");

    xmlWriter.addAttribute("id", this.uid);
    xmlWriter.addAttribute("name", this.name);
    if (this.sourceEnd !== null)
        xmlWriter.addAttribute("sourceRef", resolveTypeRef(this.sourceEnd.uid));
    if (this.targetEnd !== null)
        xmlWriter.addAttribute("targetRef", resolveTypeRef(this.targetEnd.uid));

    xmlWriter.endNode();
};

Model.prototype.toXml = function(xmlWriter) {
    xmlWriter.beginNode("model");
    xmlWriter.beginNodeChildren();

    xmlWriter.addCommentChild("Model elements go here");

    for (id in this.modelElementsMap) {
        var obj = this.modelElementsMap[id];
        if (obj !== null)
            obj.toXml(xmlWriter);
    }

    xmlWriter.endNodeChildren();
    xmlWriter.endNode();
};